package com.opennews.openplatform.service;

import com.opennews.openplatform.entity.tables.records.RequestMapRecord;
import com.opennews.openplatform.jooq.DSLContextWrapper;
import com.opennews.openplatform.jooq.RecordHelper;
import com.opennews.openplatform.myspringbootcore.common.constant.RoleType;
import com.opennews.openplatform.myspringbootcore.security.RequestMapManager;
import lombok.RequiredArgsConstructor;
import org.jooq.DSLContext;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.opennews.openplatform.entity.Tables.REQUEST_MAP;

@RequiredArgsConstructor
@Service
public class RequestMapService {
    private final DSLContext dslContext;
    private final DSLContextWrapper dslContextWrapper;

    /**
     * Prepares the initial data for Role.
     */
    public void init() {
        if (dslContextWrapper.count(REQUEST_MAP) == 0) {
            List<RequestMapRecord> records = new ArrayList<>();

            records.add(createRecordForAll("/app-error-report/**", null));
            records.add(createRecordForAll("/authentication/**", null));

            records.add(createRecordForAuthenticated("/map-location/**", null));
            records.add(createRecordForAuthenticated("/file/**", null));
            records.add(createRecordForAuthenticated("/join-account-group-request/**", null));
            records.add(createRecordForAuthenticated("/user/**", null));
            records.add(createRecordForAuthenticated("/user-active-token/**", null));
            records.add(createRecordForAuthenticated("/app-release-log/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/account-group/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/account-group-user/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/bank/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/bank-card/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/data-initialization/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/income-expense-analysis/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/income-expense-detail/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/income-expense-detail-type/query-by-account-group", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/income-expense-detail-type/query-analysis-not-included-detail-types", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/journey/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/journey-income-expense-detail/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/region/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/token-check/**", HttpMethod.GET.name()));
            records.add(createRecordForAuthenticated("/authentication/unregister", HttpMethod.POST.name()));
            records.add(createRecordForAuthenticated("/account-group-user/quit", HttpMethod.POST.name()));

            records.add(createRecordForUserAndAdmin("/income-expense-detail/**", HttpMethod.POST.name()));
            records.add(createRecordForUserAndAdmin("/journey/**", HttpMethod.POST.name()));
            records.add(createRecordForUserAndAdmin("/journey-income-expense-detail/**", HttpMethod.POST.name()));

            records.add(createForAdmin("/income-expense-detail-type/check-existing", HttpMethod.GET.name()));
            records.add(createForAdmin("/account-group/**", HttpMethod.POST.name()));
            records.add(createForAdmin("/account-group-user/**", HttpMethod.POST.name()));
            records.add(createForAdmin("/bank-card/**", HttpMethod.POST.name()));
            records.add(createForAdmin("/file/upload-avatar", HttpMethod.POST.name()));
            records.add(createForAdmin("/income-expense-detail-type/**", HttpMethod.POST.name()));
            records.add(createForAdmin("/join-account-group-request/send", HttpMethod.POST.name()));
            records.add(createForAdmin("/role/query-non-system-level-roles", HttpMethod.POST.name()));
            records.add(createForAdmin("/user/query-users-not-in-account-group", HttpMethod.GET.name()));
            records.add(createForAdmin("/user/add", HttpMethod.POST.name()));
            records.add(createForAdmin("/user/update-password", HttpMethod.POST.name()));
            records.add(createForAdmin("/user/update-enabled", HttpMethod.POST.name()));
            records.add(createForAdmin("/user/delete", HttpMethod.POST.name()));

            dslContext.batchInsert(records).execute();
        }
    }

    /**
     * Extracts role names from request map config attribute.
     * Example: hasRole('admin'), hasAnyRole('reporter', 'planner').
     *
     * @param configAttribute: Property configAttribute of request map.
     * @return String list of role name.
     */
    public List<String> extractRoleNames(String configAttribute) {
        List<String> roleNames = new ArrayList<>();

        // This would match the role names, like 'admin' or 'reporter', 'planner'.
        // Regular expression with string concatenation for the prefixes.
        String regex = "(?:"
                + RequestMapManager.HAS_ROLE_PREFIX + "|"
                + RequestMapManager.HAS_ANY_ROLE_PREFIX + "|"
                + RequestMapManager.HAS_NO_ROLE_PREFIX + "|"
                + RequestMapManager.HAS_NO_ANY_ROLE_PREFIX +
                ")\\((.+)\\)";

        // Create a Pattern object
        Pattern pattern = Pattern.compile(regex);

        // Create a matcher object
        Matcher matcher = pattern.matcher(configAttribute);

        // If match found.
        if (matcher.find()) {
            // Get the matched roles string (inside the parentheses)
            String matchedRoles = matcher.group(1);

            // Remove all single quotes
            matchedRoles = matchedRoles.replace("'", "");

            // Remove all double quotes
            matchedRoles = matchedRoles.replace("\"", "");

            // Split by comma
            String[] splitedRoles = matchedRoles.split(",");

            // Trim each role and add it to the roleNames list
            for (String role : splitedRoles) {
                roleNames.add(role.trim());
            }
        }

        return roleNames;
    }

    /**
     * Checks if security request map configAttribute contains any role in roleNames.
     *
     * @param roleNames:      String list of role names.
     * @param configAttribute String of config attribute. Example: hasAnyRole('ROLE_ACCOUNT_GROUP_USER','ROLE_ACCOUNT_GROUP_ADMIN')
     */
    public boolean containsAnyRoleInConfigAttribute(List<String> roleNames, String configAttribute) {
        return containsAnyRoleInConfigRoleNames(roleNames, extractRoleNames(configAttribute));
    }

    private RequestMapRecord createRecordForAll(String url, String httpMethod) {
        return createRecord(RequestMapManager.PERMIT_ALL, url, httpMethod);
    }

    private RequestMapRecord createRecordForAuthenticated(String url, String httpMethod) {
        return createRecord(RequestMapManager.IS_AUTHENTICATED, url, httpMethod);
    }

    private RequestMapRecord createRecordForUserAndAdmin(String url, String httpMethod) {
        return createRecord(RequestMapManager.hasAnyRole(Arrays.asList(
                RoleType.ROLE_ACCOUNT_GROUP_ADMIN,
                RoleType.ROLE_ACCOUNT_GROUP_USER)), url, httpMethod);
    }

    private RequestMapRecord createForAdmin(String url, String httpMethod) {
        return createRecord(RequestMapManager.hasRole(RoleType.ROLE_ACCOUNT_GROUP_ADMIN), url, httpMethod);
    }

    private RequestMapRecord createRecord(String configAtribute, String url, String httpMethod) {
        RequestMapRecord record = dslContext.newRecord(REQUEST_MAP);
        RecordHelper.setCommonFields(record);
        record.setConfigAttribute(configAtribute);
        record.setUrl(url);
        record.setHttpMethod(httpMethod);

        return record;
    }

    /**
     * Checks if configuredRoleNames contains any role in roleNames.
     *
     * @param roleNames:       String list of role names.
     * @param configRoleNames: String list of configured role names.
     * @return True means condition meets. False means NOT.
     */
    private boolean containsAnyRoleInConfigRoleNames(List<String> roleNames, List<String> configRoleNames) {
        boolean containsAnyRole = false;

        for (String roleName : roleNames) {
            for (String configRoleName : configRoleNames) {
                if (roleName.equalsIgnoreCase(configRoleName)) {
                    containsAnyRole = true;
                    break;
                }
            }
        }

        return containsAnyRole;
    }
}
